#include "mesh.hpp"
#include <limits.h>
#include <iostream>

Mesh::Mesh(const std::vector<Point3D>& verts,
           const std::vector< std::vector<int> >& faces)
  : m_verts(verts),
    m_faces(faces)
{
	box_min = Point3D(INT_MAX,INT_MAX,INT_MAX);
	box_max = Point3D(INT_MIN,INT_MIN,INT_MIN);
	double x, y, z;
	for(std::vector<Point3D>::const_iterator p=m_verts.begin();p!=m_verts.end();p++){
		x = (*p)[0];
		y = (*p)[1];
		z = (*p)[2];
		if(x <= box_min[0]) box_min[0] = x;
		if(y <= box_min[1]) box_min[1] = y;
		if(z <= box_min[2]) box_min[2] = z;
		if(x >= box_max[0]) box_max[0] = x;
		if(y >= box_max[1]) box_max[1] = y;
		if(z >= box_max[2]) box_max[2] = z;	
	}

	Vector3D diff = box_max - box_min;
	m_box = new NonhierBox(box_min, -1, diff);
}

bool Mesh::RayIntersect(Ray ray, Intersection *intersection)
{
	bool hit;
	Intersection box_intersection = *intersection;
	bool inside = check_inside_box(ray.get_position());	
	hit = m_box->RayIntersect(ray, &box_intersection);
	if(!hit && !inside) return false;
#ifdef BOUNDING
	if(hit){
		*intersection = box_intersection;
		return true;
	}
#endif
	hit = false;
	for(std::vector<Face>::const_iterator f=m_faces.begin(); f!=m_faces.end(); f++){
		Face face = *f;
		Point3D p1 = m_verts[face[0]];
		Point3D p2 = m_verts[face[1]];
		Point3D p3 = m_verts[face[2]];
		Vector3D normal = (p2-p1).cross(p3-p1);	
		normal.normalize();
		Vector3D V = ray.get_direction();		
		double dot_product = normal.dot(V);
		if(dot_product >= 0.0) continue; // parallel or intersect from behind 
		
		// u = -(n.(p-p1))/(n.dir)
		double u = -(ray.get_position() - p1).dot(normal)/dot_product;
		if(u > 1e-6 && u < intersection->m_dist){
			bool success = true;
			Point3D p = ray.get_position() + u*V;
			for(unsigned int i=0; i<face.size(); i++){
				Point3D p1 = m_verts[face[i%face.size()]];
				Point3D p2 = m_verts[face[(i+1)%face.size()]];
				if(!check_same_normal(p1,p2,p,normal)){
					success = false;
					break;
				}
			}
			if(success){
				intersection->m_dist = u;
				intersection->m_intersect = p;
				intersection->m_normal = normal;
				intersection->m_normal.normalize();				
				hit = true;
			}
		}

	}
	return hit;
}

bool Mesh::check_same_normal(Point3D p1,Point3D p2,Point3D p3,Vector3D n)
{
	Vector3D normal = (p2-p1).cross(p3-p1);
	normal.normalize();
	return n.dot(normal) >= 0.0?true:false;
}

bool Mesh::check_inside_box(Point3D pos)
{
	bool insideX = (pos[0] > box_min[0]-0.1 && pos[0] < box_max[0]+0.1);
	bool insideY = (pos[1] > box_min[1]-0.1 && pos[1] < box_max[1]+0.1);
	bool insideZ = (pos[2] > box_min[2]-0.1 && pos[2] < box_max[2]+0.1);
	return insideX && insideY && insideZ;
}

std::ostream& operator<<(std::ostream& out, const Mesh& mesh)
{
  std::cerr << "mesh({";
  for (std::vector<Point3D>::const_iterator I = mesh.m_verts.begin(); I != mesh.m_verts.end(); ++I) {
    if (I != mesh.m_verts.begin()) std::cerr << ",\n      ";
    std::cerr << *I;
  }
  std::cerr << "},\n\n     {";
  
  for (std::vector<Mesh::Face>::const_iterator I = mesh.m_faces.begin(); I != mesh.m_faces.end(); ++I) {
    if (I != mesh.m_faces.begin()) std::cerr << ",\n      ";
    std::cerr << "[";
    for (Mesh::Face::const_iterator J = I->begin(); J != I->end(); ++J) {
      if (J != I->begin()) std::cerr << ", ";
      std::cerr << *J;
    }
    std::cerr << "]";
  }
  std::cerr << "});" << std::endl;
  return out;
}
